#include "pch.h"
#include "Scene.h"
#include "DXContext.h"
#include "PerformanceTimer.h"
#include "ErrorCodes.h"
#include "Helper.h"

#include <ppltasks.h>
#include <iostream>
#include <intrin.h>
#include <WinBase.h>
#include <ppltasks.h>

using namespace DXBase;
using namespace DXBase::Samples;
using namespace DXBase::D3D;
using namespace Platform;
using namespace Concurrency;

using namespace Windows::UI;
using namespace Microsoft::WRL;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::Foundation;

Scene::Scene()
	: debug(L"Scene")
{
	Init(ref new DXContext());
}
Scene::Scene(DXContext^ ctxt)
	: debug(L"Scene")
{
	if (!ctxt)
		throw ref new NullReferenceException();
	Init(ctxt);
}
void Scene::Init(DXContext^ ctxt)
{
	mContext = ctxt;
	Background = Colors::Black;
	mTimer = ref new PerformanceTimer();
	mParts = ref new Platform::Collections::Vector<ISceneData^>();
	RegisterPropertyChangedSafely(ctxt, ref new PropertyChangedEventHandler(this, &Scene::OnContextPropertyChanged), pctoken);

	loadingwait = CreateEventEx(NULL, NULL, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS);
	SetEvent(loadingwait);
}
void Scene::OnContextPropertyChanged(Platform::Object^ source, Windows::UI::Xaml::Data::PropertyChangedEventArgs^ args)
{
	ISceneData^ item;
	std::shared_ptr<ScenePartData> data;
	for (int i=0, N=mParts->Size; i<N; i++)
	{
		GetAt(i, item, data);
		switch (Context->Status)
		{
		case ContextStatus::CreatingDevice:
			OnLoad(item, data);
			break;
		case ContextStatus::CreatingTargetResources:
			if (Context->Target)
				OnResize(item, data);
			break;
		}
	}
}

Scene::~Scene()
{
	mParts = nullptr;
	while (partsData.size() > 0)
	{
		auto pd = partsData.back();
		partsData.pop_back();
		pd.reset();
	}
	CloseHandle(loadingwait);
	Context->PropertyChanged -= pctoken;
}

IAsyncAction^ Scene::LoadAsync()
{
	return create_async([this]
	{
		WaitForSingleObjectEx(this->loadingwait, INFINITE, TRUE);
	});
}
bool Scene::IsLoaded::get() { return loadingcount == 0; } 

void Scene::Add(ISceneData^ part)
{
	if (!part)
		return;

	mParts->Append(part);
	part->Reset();

	auto dat = std::shared_ptr<ScenePartData>(new ScenePartData());
	partsData.push_back(dat);

	OnLoad(part, dat);
}
void Scene::RemoveAt(int index)
{
	ISceneData^ item;
	std::shared_ptr<ScenePartData> data;
	GetAt(index, item, data);

	mParts->RemoveAt(index);
	partsData.erase(partsData.begin() + index);

	data.reset();  // delete the memory
	item->Reset(); // detach the part from the context
}

void Scene::RenderFrame(SceneRenderArgs^ args)
{
	if (!args)
		throw ref new NullReferenceException();

	auto ctxt = Context;
	if (ctxt->Status != ContextStatus::Ready)
		return;

	if (args->UpdateTimer)
		Timer->Update();

	ctxt->Rendering = true;
	if (!ctxt->Rendering)
		return;

	if(args->PaintBackground)
	{
		float dxbg[] = { Background.R / 256.0f, Background.G / 256.0f, Background.B / 256.0f, Background.A / 256.0f };
		ctxt->m_d3dContext->ClearRenderTargetView(ctxt->nativeTarget->m_renderTargetView.Get(), dxbg);
	}

	ISceneData^ item;
	std::shared_ptr<ScenePartData> data;
	for (int i=0, N=mParts->Size; i<N; i++)
	{
		// IMPORTAT: BUG FIX this flush instruction make sure D2D rendering is not overwritten 
		// by D3D rendering code submitted before but executed after...
		ctxt->Flush();

		// draw scene
		GetAt(i, item, data);
		OnRender(item, data);
	}

	ctxt->Rendering = false;
}

void Scene::GetAt(int index, ISceneData^& item, std::shared_ptr<ScenePartData>& data)
{
	item = mParts->GetAt(index);
	data = partsData[index];
}
void Scene::OnLoad(ISceneData^ sc, std::shared_ptr<ScenePartData> pd)
{
	if (pd.get()->loaded || pd.get()->loading)
		return;
	pd.get()->loading = true;
	auto op = sc->LoadAsync(Context);
	switch (op->Status)
	{
	case AsyncStatus::Canceled:
	case AsyncStatus::Error:
		break;
	case AsyncStatus::Completed:
		pd.get()->loaded = op->GetResults();
		break;
	case AsyncStatus::Started:
		ResetEvent(loadingwait);
		InterlockedIncrement16(&loadingcount);
		create_task(op).then([this, pd] (task<bool> b) 
		{
			if (!loadingcount)
				throw ref new FailureException();
			auto res = InterlockedDecrement16(&loadingcount);
			if (!res)
				SetEvent(loadingwait);

			try { pd.get()->loaded = b.get(); }
			catch (Exception^ ex)
			{
				auto msg = "Exception: " + ex->ToString() + "\r\n";
				OutputDebugString(msg->Data());
			}
		});
		break;
	}
}
void Scene::OnReset(ISceneData^ sc, std::shared_ptr<ScenePartData> pd)
{
	pd.get()->sized = false;
	pd.get()->loaded = false;
	pd.get()->loading = false;
	sc->Reset();
}
void Scene::OnResize(ISceneData^ sc, std::shared_ptr<ScenePartData> pd)
{
	pd->sized = false;
	if (!pd->loaded)
		return;
	sc->Resize(Context);
	pd.get()->sized = true;
}
void Scene::OnRender(ISceneData^ sc, std::shared_ptr<ScenePartData> pd)
{
	if (!pd.get()->loaded)
	{
		OnLoad(sc, pd);
		if (!pd.get()->loaded)
			return;
	}
	if (!pd.get()->sized)
		OnResize(sc, pd);
	sc->Render(Context, Timer);
}
